package main

import (
	"bytes"
	"encoding/binary"
	"errors"
	"fmt"
	"log"
	"os"
	"strconv"
	"strings"

	"gitee.com/RickieL/comm"
	"github.com/urfave/cli"
)

const (
	// IndexBlockLength IndexBlock长度
	IndexBlockLength int64 = 12

	// SuperBlockLength 超级块的长度
	SuperBlockLength int64 = 8

	// TotalHeaderLength TotalHeader 长度  16k,可以存储2048个index particon
	TotalHeaderLength int64 = 1024 * 16

	// DataStartPoint 数据开始的位置
	DataStartPoint int64 = SuperBlockLength + TotalHeaderLength
)

func main() {

	app := cli.NewApp()
	app.Name = "goMaker"
	app.Usage = "生成ip数据文件"
	app.Author = "yongfu"
	app.Description = "生成ip数据文件"
	app.UsageText = "./goMaker <-s ../../data/ip.merge.txt>"

	app.Flags = []cli.Flag{
		cli.StringFlag{
			Name:     "source, s",
			Usage:    "ip来源文件",
			Required: true,
		},
	}

	app.Action = func(c *cli.Context) {
		source := c.String("source")
		dest := "ip2region-go.db"

		if source == "" {
			fmt.Printf("./goMaker <-s ../../data/ip.merge.txt>, 传入参数不能为空.")
			return
		} else if !comm.IsFile(source) {
			fmt.Printf("./goMaker <-s ../../data/ip.merge.txt>, %v 不是一个文件.", source)
			return
		}
		err := makeDB(source, dest)
		if err != nil {
			fmt.Printf("生成失败了. %v", err)
			return
		}

		fmt.Println("success")

	}
	app.Run(os.Args)
}

func makeDB(source, dest string) error {
	var err error

	// IndexStartPos index开始的位置
	var IndexStartPos int64

	// 获取所有源数据
	text, _ := comm.ReadFileByLine(source)
	Locs := make(map[string]Location, 1)
	IpInfos := []IpInfo{}
	for _, v := range text {
		ipInfoLine := strings.SplitN(v, "|", 3)
		ipstart, _ := ip2long(ipInfoLine[0])
		ipend, _ := ip2long(ipInfoLine[1])
		IpInfos = append(IpInfos, IpInfo{
			IpStart:     ipInfoLine[0],
			IpStartLong: ipstart,
			IpEnd:       ipInfoLine[1],
			IpEndLong:   ipend,
			Loc:         ipInfoLine[2],
		})

		Locs[ipInfoLine[2]] = Location{
			Length: len(ipInfoLine[2]),
		}
	}

	// 创建二进制文件
	file, err := os.Create(dest)
	defer file.Close()
	if err != nil {
		log.Fatal(err)
		fmt.Printf("%#v \n", err)
		return err
	}

	// 写入data信息
	DataCurrPos := DataStartPoint
	lenLocs := len(Locs)
	start := 0
	for k, v := range Locs {
		start++
		//v.PointAddr = DataCurrPos
		Locs[k] = Location{
			Length:    v.Length,
			PointAddr: DataCurrPos,
		}
		_, err := file.WriteAt([]byte(k), DataCurrPos)
		if err != nil {
			fmt.Printf("WriteAt err: %v \n", err)
		}

		DataCurrPos += int64(v.Length)
		//fmt.Printf("n: %v, v.Length: %v\n\n", n, v.Length)
		if start == lenLocs {
			IndexStartPos = DataCurrPos
		}
	}

	// 写入first index ptr
	var binBuf bytes.Buffer
	binary.Write(&binBuf, binary.BigEndian, uint32(IndexStartPos))
	_, err = file.WriteAt(binBuf.Bytes(), 0)
	if err != nil {
		fmt.Printf("WriteAt err: %v \n", err)
	}

	// 写入index partion, 4k
	IndexCurrPos := IndexStartPos
	var IndexEndPos int64 = 0
	var IndexBlockCount int64 = 0
	var IndexHeaderBlockPos int64 = 8
	lenIPs := len(IpInfos)
	for i, v := range IpInfos {

		// 当 i % 341 (4*1024/12 ≈ 341 ) = 0 时， 更新header index block的信息
		if i%341 == 0 {
			var headIndex []byte
			headIndex = make([]byte, 8)
			binary.BigEndian.PutUint32(headIndex[0:], uint32(v.IpStartLong))
			binary.BigEndian.PutUint32(headIndex[4:], uint32(IndexCurrPos))
			file.WriteAt(headIndex, IndexHeaderBlockPos)
			IndexHeaderBlockPos += 8

			//fmt.Printf("IpStartLong: %v IndexCurrPos: %v, %x\n", v.IpStartLong, IndexCurrPos, IndexCurrPos)
		}

		// 初始化index block的内容
		fmt.Printf("IpStartLong: %v, IndexCurrPos: %x\n", v.IpStartLong, IndexCurrPos)
		//var ipblock []byte
		ipblock := make([]byte, 8)
		binary.BigEndian.PutUint32(ipblock[0:], uint32(v.IpStartLong))
		binary.BigEndian.PutUint32(ipblock[4:], uint32(v.IpEndLong))
		file.WriteAt(ipblock, IndexCurrPos)
		IndexCurrPos += 8

		dataLenBufSlice := make([]byte, 0, 1)
		c := *bytes.NewBuffer(dataLenBufSlice)
		dataPosBufSlice := make([]byte, 0, 3)
		d := *bytes.NewBuffer(dataPosBufSlice)

		var dataLenBuf bytes.Buffer
		var dataPosBuf bytes.Buffer

		dataLenBuf = c
		dataPosBuf = d

		binary.Write(&dataLenBuf, binary.BigEndian, uint8(Locs[v.Loc].Length))
		file.WriteAt(dataLenBuf.Bytes(), IndexCurrPos)
		IndexCurrPos++
		binary.Write(&dataPosBuf, binary.BigEndian, uint32(Locs[v.Loc].PointAddr))
		file.WriteAt(dataPosBuf.Bytes()[1:], IndexCurrPos)
		IndexCurrPos += 3

		// 如果是最后一行记录， 则记录下来最后的block的位置
		if (i + 1) == lenIPs {
			IndexEndPos = IndexCurrPos - 12
		}

		// 最后对是否跳转到下个index partion(4k)
		if (i+1)%341 == 0 {
			IndexBlockCount++
			IndexCurrPos = IndexStartPos + 4096*IndexBlockCount
		}

	}

	// 写入first end ptr
	var binEndBuf bytes.Buffer
	binary.Write(&binEndBuf, binary.BigEndian, uint32(IndexEndPos))
	file.WriteAt(binEndBuf.Bytes(), 4)
	//fmt.Printf("Locs: %#v , IndexEndPos: %v\n", Locs, IndexEndPos)

	return err
}

// IpInfo 从源文件中读取的行内容
type IpInfo struct {
	IpStart     string
	IpStartLong int64
	IpEnd       string
	IpEndLong   int64
	Loc         string
}

// Location ip地址信息
type Location struct {
	Length    int
	PointAddr int64
}

func ip2long(IpStr string) (int64, error) {
	bits := strings.Split(IpStr, ".")
	if len(bits) != 4 {
		return 0, errors.New("ip format error")
	}

	var sum int64
	for i, n := range bits {
		bit, _ := strconv.ParseInt(n, 10, 64)
		sum += bit << uint(24-8*i)
	}

	return sum, nil
}
